package com.tong.hao.common.configuration.sftp;

import cn.buddha.jec.util.TongBeanUtils;
import com.jcraft.jsch.ChannelSftp;
import com.jcraft.jsch.JSch;
import com.jcraft.jsch.JSchException;
import com.jcraft.jsch.Session;
import com.jcraft.jsch.SftpATTRS;
import com.jcraft.jsch.SftpException;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Properties;

/**
 * @ClassName SftpUtil
 * @Author 友野浩二
 * @Date 2019/11/30 11:42
 * @Description SFTP文件上传下载工具
 * @Version 1.0
 */
@Component
@Slf4j
public class SftpUtil {

    @Autowired
    private TongSftpConfig tongSftpConfig;

    /**
     * 规避多线程并发不断开问题
     */
    private volatile static ThreadLocal<SftpUtil> sftpLocal = new ThreadLocal<>();

    /**
     * sftp通道
     */
    private ChannelSftp channel;

    /**
     * 会话
     */
    private Session session;

    /**
     * 路径分隔符
     */
    private static final String FOLDER_SEPARATOR = "/";

    /**
     * sftp连接失败message
     */
    private static final String SFTP_FAIL = "获取SFTP连接失败,请检查: {}@, {}:, {}, {}";

    /**
     * @Author 友野浩二
     * @Description // 初始化,创建一个新的 SFTP 通道
     * @Date 2019/11/30 20:20
     * @Param [sftpIp, sftpPort, sftpUsername, sftpPassword]
     **/
    private void init(String sftpIp, Integer sftpPort,
                      String sftpUsername, String sftpPassword,
                      Integer timeout) {
        // 场景 JSch 对象
        JSch jSch = new JSch();
        try {
            // 获取 sshSession
            this.session = jSch.getSession(sftpUsername, sftpIp, sftpPort);
            // 设置密码
            this.session.setPassword(sftpPassword);
            // 设置超时
            this.session.setTimeout(timeout);
            // 第一次登录时候提示(ask|yes|no)
            Properties config = new Properties();
            config.put("StrictHostKeyChecking", "no");
            config.put("compression.s2c", "zlib,none");
            config.put("compression.c2s", "zlib,none");
            this.session.setConfig(config);
            // 开启 sshSession 连接
            this.session.connect();

            // 打开 SFTP 通道
            this.channel = (ChannelSftp) this.session.openChannel("sftp");
            // 建立 SFTP 通道的连接
            this.channel.connect();
        } catch (JSchException e) {
            log.error("init sftp exception,maybe get connect error,please check (username and password) or reload sftp server", e);
        }
    }

    /**
     * @return com.tong.hao.common.configuration.sftp.SftpUtil
     * @Author 友野浩二
     * @Description // 获取本地线程存储的sftp客户端，使用完必须调用 release()释放连接
     * @Date 2019/11/30 19:17
     * @Param []
     **/
    public SftpUtil getSftpUtil() {
        SftpUtil sftpUtil = sftpLocal.get();
        // 判断是否已经连接
        if (null == sftpUtil || !sftpUtil.isConnected()) {
            SftpUtil sftpUtilTemp = new SftpUtil();
            sftpUtilTemp.init(tongSftpConfig.getIp(), tongSftpConfig.getPort(),
                    tongSftpConfig.getUsername(), tongSftpConfig.getPassword(), tongSftpConfig.getTimeout());
            sftpLocal.set(sftpUtilTemp);
        }
        return sftpLocal.get();
    }

    /**
     * @return boolean
     * @Author 友野浩二
     * @Description // 上传文件
     * @Date 2019/12/1 14:04
     * @Param [file, remotePath]
     **/
    public boolean upload(final File file, final String remotePath) {
        if (TongBeanUtils.isNullObj(this.channel)) {
            log.error(SFTP_FAIL, tongSftpConfig.getIp(), tongSftpConfig.getPort(),
                    tongSftpConfig.getUsername(), tongSftpConfig.getPassword());
            return false;
        } else {
            FileInputStream fileInputStream = null;
            try {
                if (file.isFile()) {
                    // 创建文件目录
                    createDir(remotePath);
                    // 进入并设置为当前目录
                    this.channel.cd(remotePath);
                    log.info("远程SFTP服务器路径: {}", remotePath);
                    fileInputStream = new FileInputStream(file);
                    // 上传文件
                    this.channel.put(fileInputStream, file.getName());
                    return true;
                }
            } catch (FileNotFoundException e) {
                log.error("SFTP上传文件么有找到: ", e);
                return false;
            } catch (SftpException e) {
                log.error("SFTP上传文件服务器异常: ", e);
                return false;
            } finally {
                try {
                    if (null != fileInputStream) {
                        fileInputStream.close();
                    }
                } catch (IOException e) {
                    log.error("文件上传输入流关闭异常: ", e);
                }
            }
        }
        return false;
    }

    /**
     * @return java.io.File
     * @Author 友野浩二
     * @Description // 下载文件
     * @Date 2019/12/1 19:58
     * @Param [rootDir, filePath]
     **/
    public File downFile(String rootDir, String filePath) {
        if (TongBeanUtils.isNullObj(this.channel)) {
            log.error(SFTP_FAIL, tongSftpConfig.getIp(), tongSftpConfig.getPort(),
                    tongSftpConfig.getUsername(), tongSftpConfig.getPassword());
            return null;
        }
        OutputStream outputStream = null;
        File file;
        try {
            this.channel.cd(rootDir);
            String folder = System.getProperty("java.io.tmpdir");
            file = new File(folder + File.separator + filePath.substring(filePath.lastIndexOf(FOLDER_SEPARATOR) + 1));
            outputStream = new FileOutputStream(file);
            this.channel.get(filePath, outputStream);
        } catch (FileNotFoundException e) {
            log.error("SFTP下载文件么有找到: ", e);
            file = null;
        } catch (SftpException e) {
            log.error("SFTP下载文件服务器异常: ", e);
            file = null;
        } finally {
            if (null != outputStream) {
                try {
                    outputStream.close();
                } catch (IOException e) {
                    log.error("文件下载输出流关闭异常: ", e);
                }
            }
        }
        return file;
    }

    /**
     * @Author 友野浩二
     * @Description // 每次连接必须释放资源(释放本地线程存储的sftp客户端)
     * @Date 2019/11/30 21:37
     * @Param []
     **/
    public void release() {
        if (null != sftpLocal.get()) {
            sftpLocal.get().closeChannel();// 关闭通道
            // TODO remove()与set(null)的区别
            sftpLocal.set(null);
            sftpLocal.remove();
        }
    }

    /**
     * @Author 友野浩二
     * @Description // 关闭通道
     * @Date 2019/11/30 21:29
     **/
    public void closeChannel() {
        if (null != this.channel) {
            try {
                this.channel.disconnect();
            } catch (Exception e) {
                log.error("close SFTP channel found exception: ", e);
            }
        }

        if (null != this.session) {
            try {
                this.session.disconnect();
            } catch (Exception e) {
                log.error("close SFTP session found exception: ", e);
            }
        }
    }

    /**
     * @Author 友野浩二
     * @Description // 创建一个文件目录
     * @Date 2019/12/1 14:10
     * @Param [createPath]
     **/
    public void createDir(final String createPath) {
        try {
            if (isDirExist(createPath)) {
                this.channel.cd(createPath);
                return;
            }
            // 剔除空格
            String[] pathArray = StringUtils.split(createPath, FOLDER_SEPARATOR);
            StringBuffer filePath = new StringBuffer(FOLDER_SEPARATOR);
            for (String path : pathArray) {
                filePath.append(path).append(FOLDER_SEPARATOR);
                // 判断目录是否存在
                if (isDirExist(filePath.toString())) {
                    this.channel.cd(filePath.toString());
                } else {
                    // 建立目录
                    this.channel.mkdir(filePath.toString());
                    // 进入并设置为当前目录
                    this.channel.cd(filePath.toString());
                }
            }
            this.channel.cd(createPath);
        } catch (SftpException e) {
            log.error("createDir exception: ", e);
        }
    }

    /**
     * @return boolean
     * @Author 友野浩二
     * @Description // 判断目录是否存在
     * @Date 2019/12/1 14:16
     * @Param [directory]
     **/
    public boolean isDirExist(final String directory) {
        boolean isDirExistFlag = false;
        try {
            SftpATTRS sftpAttrs = this.channel.lstat(directory);
            isDirExistFlag = sftpAttrs.isDir();
        } catch (SftpException e) {
            log.error("no such file: ", e);
        }
        return isDirExistFlag;
    }

    /**
     * @return java.lang.Boolean
     * @Author 友野浩二
     * @Description // 是否已连接
     * @Date 2019/11/30 19:24
     * @Param []
     **/
    private Boolean isConnected() {
        return null != channel && channel.isConnected();
    }

}
